package com.lry.util.requestcore;

import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.Method;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.lry.util.requestcore.util.DbUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * @author ALI
 * @since 2023/5/16
 */
@Slf4j
public abstract class GSimpleRequest {
    public static final String LOG_PRE = "GSimple请求 - ";
    private static final ConcurrentHashSet<String> createTable = new ConcurrentHashSet<>();
    protected String token;

    public JSONArray request(Method method, String api, Map<String, Object> param, String dataFieldName) {
        if (method == Method.GET) {
            return get(api, param, dataFieldName);
        } else {
            return post(api, param, dataFieldName);
        }
    }

    public JSONArray get(String api, Map<String, Object> param, String dataFieldName) {
        HttpResponse httpResponse = MyHttpUtil.get(api, getHeader(getToken(api)), param);
        if (this.isLogin(httpResponse)) {
            String token = login();
            if (StringUtils.isNotBlank(token)) {
                setToken(api, token);
                httpResponse = MyHttpUtil.get(api, getHeader(token), param);
            }
        }
        MyHttpUtil.check(httpResponse);
        return MyHttpUtil.getArrayObject(httpResponse.body(), dataFieldName);
    }

    public JSONArray post(String api, Object param, String dataFieldName) {
        HttpResponse httpResponse = MyHttpUtil.post(api, getHeader(getToken(api)), param);
        if (this.isLogin(httpResponse)) {
            String token = login();
            if (StringUtils.isNotBlank(token)) {
                setToken(api, token);
                httpResponse = MyHttpUtil.post(api, getHeader(token), param);
            }
        }
        MyHttpUtil.check(httpResponse);
        return MyHttpUtil.getArrayObject(httpResponse.body(), dataFieldName);
    }

    /**
     * 分页递归查询，并执行报错到数据库
     *
     * @param method        方法
     * @param api           请求地址
     * @param pageParam     参数
     * @param dataFieldName 响应的数据字段
     * @param tableName     表名
     * @param idField       id字段
     * @param pageNumName   递归的分页页码字段名称
     */
    public void subPage(Method method, String api, Map<String, Object> pageParam, String dataFieldName,
                        String tableName, String idField, String pageNumName) {
        subPage(method, api, pageParam, dataFieldName, tableName, idField, pageNumName, null, null);
    }

    /**
     * 分页递归查询，并执行报错到数据库，并执行consumer
     *
     * @param method        方法
     * @param api           请求地址
     * @param pageParam     参数
     * @param dataFieldName 响应的数据字段
     * @param tableName     表名
     * @param idField       id字段
     * @param pageNumName   递归的分页页码字段名称
     * @param consumer      消费者
     */
    public void subPage(Method method, String api, Map<String, Object> pageParam, String dataFieldName,
                        String tableName, String idField, String pageNumName, Consumer<JSONObject> consumer) {
        subPage(method, api, pageParam, dataFieldName, tableName, idField, pageNumName, null, consumer);
    }

    /**
     * 分页递归查询，并执行consumer方法，不进行保存
     *
     * @param method        请求方式
     * @param api           请求地址
     * @param pageParam     请求参数
     * @param dataFieldName 响应的数据字段
     * @param pageNumName   分页页码字段
     * @param consumer      消费者
     */
    public void subPageNoSave(Method method, String api, Map<String, Object> pageParam, String dataFieldName,
                              String pageNumName, Consumer<JSONObject> consumer) {
        log("分页：{}", pageParam);
        JSONArray objects = request(method, api, pageParam, dataFieldName);
        if (objects.isEmpty()) {
            log("数据为空！");
            return;
        }
        Optional.ofNullable(consumer).ifPresent(d -> objects.forEach(dd -> d.accept((JSONObject) dd)));
        increPage(pageParam, pageNumName);
        subPageNoSave(method, api, pageParam, dataFieldName, pageNumName, consumer);
    }

    /**
     * 分页递归查询，并执行报错到数据库，并执行consumer
     *
     * @param method        方法
     * @param api           请求地址
     * @param pageParam     参数
     * @param dataFieldName 响应的数据字段
     * @param tableName     表名
     * @param idField       id字段
     * @param pageNumName   递归的分页页码字段名称
     * @param preFilter     前置过滤器
     * @param consumer      消费者
     */
    public void subPage(Method method, String api, Map<String, Object> pageParam, String dataFieldName,
                        String tableName, String idField, String pageNumName, Predicate<JSONObject> preFilter,
                        Consumer<JSONObject> consumer) {
        log("分页：{}", pageParam);
        JSONArray objects = request(method, api, pageParam, dataFieldName);
        if (objects.isEmpty()) {
            log("数据为空！");
            return;
        }
        List<Map<String, Object>> collect = null;
        if (preFilter != null) {
            collect = objects.stream().map(d -> (JSONObject) d)
                             .filter(preFilter)
                             .peek(d -> Optional.ofNullable(consumer).ifPresent(c -> c.accept(d)))
                             .collect(Collectors.toList());
        } else {
            collect = objects.stream().map(d -> (JSONObject) d)
                             .peek(d -> Optional.ofNullable(consumer).ifPresent(c -> c.accept(d)))
                             .collect(Collectors.toList());
        }
        if (!collect.isEmpty()) {
            execute(tableName, idField, collect);
        }
        increPage(pageParam, pageNumName);
        subPage(method, api, pageParam, dataFieldName, tableName, idField, pageNumName, preFilter, consumer);
    }

    public void subPage(Method method, String api, Map<String, Object> pageParam, String dataFieldName,
                        String tableName, String idField, String pageNumName, Predicate<JSONObject> preFilter,
                        Consumer<JSONObject> consumer, Predicate<JSONObject> afterFilter) {
        log("分页：{}", pageParam);
        JSONArray objects = request(method, api, pageParam, dataFieldName);
        if (objects.isEmpty()) {
            log("数据为空！");
            return;
        }
        List<Map<String, Object>> collect = null;
        if (preFilter != null) {
            collect = objects.stream().map(d -> (JSONObject) d)
                             .filter(preFilter)
                             .peek(d -> Optional.ofNullable(consumer).ifPresent(c -> c.accept(d)))
                             .collect(Collectors.toList());
        } else {
            collect = objects.stream().map(d -> (JSONObject) d)
                             .peek(d -> Optional.ofNullable(consumer).ifPresent(c -> c.accept(d)))
                             .collect(Collectors.toList());
        }
        if (afterFilter != null) {
            collect = collect.parallelStream().map(d -> (JSONObject) d).filter(afterFilter)
                             .collect(Collectors.toList());
        }
        if (!collect.isEmpty()) {
            execute(tableName, idField, collect);
        }
        increPage(pageParam, pageNumName);
        subPage(method, api, pageParam, dataFieldName, tableName, idField, pageNumName, preFilter, consumer);
    }

    /**
     * @param tableName   表名
     * @param pageNum     页码，从1开始
     * @param pageSize    页大小
     * @param selectField 查询的字段，可以为空
     */
    public List<Map<String, Object>> limitPage(String tableName, int pageNum, int pageSize, String... selectField) {
        String select = "*";
        if (selectField != null && selectField.length != 0) {
            select = Arrays.stream(selectField).map(d -> "`" + d + "`").collect(Collectors.joining(","));
        }
        String format = "select %s from `%s` limit %s,%s";
        int offset = (pageNum - 1) * pageSize;
        String sql = String.format(format, select, tableName, offset, pageSize);
        List<Map<String, Object>> maps = null;
        try {
            maps = getJdbcTemplate().queryForList(sql);
        } catch (DataAccessException e) {
            e.printStackTrace();
            return Collections.emptyList();
        }
        return maps;
    }

    public void execute(String tableName, String idField, JSONArray objects) {
        List<Map<String, Object>> data = new ArrayList<>();
        for (Object object : objects) {
            JSONObject jb = (JSONObject) object;
            setNull(jb);
            data.add(jb);
        }
        execute(tableName, idField, data);
    }

    public void execute(String tableName, String idField, JSONArray objects, Set<String> filterFields) {
        List<Map<String, Object>> data = new ArrayList<>();
        for (Object object : objects) {
            JSONObject jb = (JSONObject) object;
            setNull(jb);
            data.add(jb);
        }
        execute(tableName, idField, data, filterFields);
    }

    public void insert(String tableName, List<Map<String, Object>> data) {
        blankToNull(data);
        String sql = SqlUtils.assembleBatchInsertSql(tableName, data, false);
        getJdbcTemplate().execute(sql);
    }

    public void execute(String tableName, String idField, List<Map<String, Object>> data) {
        blankToNull(data);
        List<String> saveSqls = SqlUtils.assembleBatchSaveSql(getJdbcTemplate(), tableName, idField, data, false);
        String sql = saveSqls.stream().filter(d -> !d.trim().startsWith("select")).collect(Collectors.joining());
        getJdbcTemplate().execute(sql);
    }

    public void execute(String tableName, String idField, List<Map<String, Object>> data, Set<String> filterFields) {
        data.forEach(d -> d.entrySet().removeIf(dd -> !filterFields.contains(dd.getKey())));
        blankToNull(data);
        List<String> saveSqls = SqlUtils.assembleBatchSaveSql(getJdbcTemplate(), tableName, idField, data, false);
        String sql = saveSqls.stream().filter(d -> !d.trim().startsWith("select")).collect(Collectors.joining());
        getJdbcTemplate().execute(sql);
    }

    public void blankToNull(List<Map<String, Object>> data) {
        data.parallelStream().forEach(this::setNull);
    }

    /**
     * 初始化表
     *
     * @param method        请求方式
     * @param api           请求地址
     * @param pageParam     参数，最好限制10条数据
     * @param dataFieldName 数据字段
     * @param tableName     表名
     * @param idField       id字段
     */
    public void initTable(Method method, String api, Map<String, Object> pageParam, String dataFieldName,
                          String tableName, String idField) {
        initTable(method, api, pageParam, dataFieldName, tableName, idField, null);
    }

    public void initTable(Method method, String api, Map<String, Object> pageParam, String dataFieldName,
                          String tableName, String idField, Consumer<JSONObject> consumer) {
        log("初始化表...");
        JSONArray objects = request(method, api, pageParam, dataFieldName);
        if (objects.isEmpty()) {
            log("数据为空 -- 初始化表失败！");
            return;
        }
        if (consumer != null) {
            objects = objects.stream().map(d -> (JSONObject) d).peek(consumer).collect(Collectors.toCollection(
                JSONArray::new));
        }
        initTable(tableName, idField, objects);
    }


    public void initTable(String tableName, String idField, JSONArray objects) {
        if (createTable.contains(tableName)) {
            return;
        }
        createTable.add(tableName);
        try {
            String createSql = DbUtil.buildCreateSql(idField, tableName, objects);
            getJdbcTemplate().execute(createSql);
        } catch (Exception e) {
            if (!e.getMessage().contains("already exists")) {
                e.printStackTrace();
            }
        }

    }

    public void initTable(String tableName, String idField, List<Map<String, Object>> objects) {
        if (createTable.contains(tableName)) {
            return;
        }
        createTable.add(tableName);
        try {
            String createSql = DbUtil.buildCreateSql(idField, tableName, objects);
            getJdbcTemplate().execute(createSql);
        } catch (Exception e) {
            if (!e.getMessage().contains("already exists")) {
                e.printStackTrace();
            }
        }
    }

    public String getToken(String url) {
        if (StringUtils.isBlank(url)) {
            return token;
        }
        if (StringUtils.isBlank(token)) {
            TokenCache.get(url);
        }
        return token;
    }

    public void setToken(String url, String token) {
        if (StringUtils.isBlank(url)) {
            this.token = token;
            return;
        }
        TokenCache.add(url, token);
    }

    private void threadPage2(Method method, int threadCount, String api, String dataFieldName, String pageNumName,
                             Consumer<JSONArray> consumer, Map<String, Object> param) {
        JSONArray list = request(method, api, param, dataFieldName);
        if (list.isEmpty()) {
            return;
        }
        consumer.accept(list);
        threadPage2(method, threadCount, api, dataFieldName, pageNumName, consumer, param);
        param.put(pageNumName, (int) param.get(pageNumName) + threadCount);
    }

    private void setNull(Map<String, Object> json) {
        for (Map.Entry<String, Object> entry : json.entrySet()) {
            if (entry.getValue() == null || StringUtils.isBlank(entry.getValue().toString())) {
                entry.setValue(null);
            }
        }
    }

    /**
     * 多线程执行
     */
    protected void threadPage(Method method, int threadCount, String api, Map<String, Object> pageParam,
                              String dataFieldName, String pageNumName, Consumer<JSONArray> consumer) {
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < threadCount; i++) {
            int finalI = i;
            Thread thread = new Thread(() -> {
                Map<String, Object> temp = new HashMap<>(pageParam);
                temp.put(pageNumName, finalI);
                threadPage2(method, threadCount, api, dataFieldName, pageNumName, consumer, temp);
            });
            thread.start();
            threadList.add(thread);
        }
        threadList.forEach(d -> {
            try {
                d.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

    protected void increPage(Map<String, Object> pageParam, String pageNumName) {
        pageParam.computeIfPresent(pageNumName, (k, v) -> Integer.parseInt(v.toString()) + 1);
    }

    protected void log(String msg, Object... args) {
        XxlJobHelper.log(LOG_PRE + msg, args);
        log.info(LOG_PRE + msg, args);
    }

    protected boolean isLogin(HttpResponse httpResponse) {
        return httpResponse.getStatus() == 401;
    }

    protected abstract JdbcTemplate getJdbcTemplate();

    protected abstract Map<String, String> getHeader(String token);

    protected abstract String login();
}
